Skip to content

feat: projects as backend sources with system prompt injection#8739

Open
DOsinga wants to merge 4 commits intomainfrom
sources-projects
Open

feat: projects as backend sources with system prompt injection#8739
DOsinga wants to merge 4 commits intomainfrom
sources-projects

Conversation

@DOsinga
Copy link
Copy Markdown
Collaborator

@DOsinga DOsinga commented Apr 21, 2026

Summary

Move projects from Tauri IPC commands to the ACP sources system, making them a backend-managed entity available to all clients (desktop, CLI, TUI). Project instructions are now injected into the system prompt by the agent (cacheable, once per conversation) instead of being prepended to every user message by the frontend.

Builds on #8675 (skills as sources over ACP) by adding Project as a second source type.

Backend changes

Sources (crates/goose/src/sources.rs)

  • Add Project variant to SourceType with storage in Paths::data_dir()/projects/{name}.md
  • Projects stored as .md files with YAML frontmatter (name, description, plus arbitrary properties like icon, color, workingDirs, preferredProvider)
  • Add includeProjectSources flag to list_sources() that scans all project working directories for project-scoped skills
  • Skills found in project dirs get ephemeral projectName/projectDir properties for UI display (not persisted in the skill file)
  • 8 passing tests including integration test for project-scoped skill listing

Agent (crates/goose/src/agents/agent.rs)

  • load_project_instructions() reads project source from disk via session → thread → project_id → source file
  • reply_internal() appends project instructions to the system prompt (not user message)

ACP (crates/goose-acp/src/server.rs, crates/goose-sdk/src/custom_requests.rs)

  • New _goose/session/set_project method to update thread metadata project_id
  • Extract project_id from meta on new_session
  • projectId convenience field on CreateSourceRequest (resolves project ID → working dir server-side)
  • includeProjectSources flag on ListSourcesRequest
  • properties field on SourceEntry, CreateSourceRequest, UpdateSourceRequest

Frontend changes

Projects API (projects.ts)

  • Complete rewrite: invoke() → ACP _goose/sources/* ext method calls
  • ProjectInfo mapped to/from SourceEntry with properties bag

Chat (useChatSessionController.ts)

  • Remove buildProjectSystemPrompt from prompt composition — backend handles this now
  • Call setSessionProject when project is assigned to a session
  • Pass projectId through session preparation chain

Skills

  • CreateSkillDialog: add "Save to" dropdown (Global / Project X) for project-scoped skills
  • SkillsView: show project badge on non-global skills
  • Skills API: support projectId on create, includeProjectSources on list

Deleted

  • ui/goose2/src-tauri/src/commands/projects.rs (508 lines, 9 Tauri commands)
  • buildProjectSystemPrompt function and its tests

How it works

Frontend: setSessionProject(sessionId, "my-app")
→ ACP: _goose/session/set_project → ThreadMetadata.project_id = "my-app"

Agent reply:
→ reply_internal() → load_project_instructions(session)
→ session → thread → project_id = "my-app"
→ reads ~/.local/share/goose/projects/my-app.md
→ Appends to system prompt: "# Project: My App\n\n..."

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1eb462962d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +75 to +76
const sources = (raw.sources ?? []) as SourceEntry[];
return sources.map(toProjectInfo);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Filter archived projects from listProjects

listProjects() currently returns every project source without checking archivedAt, but the active project UIs consume this method via projectStore.fetchProjects(). After calling archiveProject(), archived entries stay in the normal project lists and remain selectable instead of moving out of active views. Filter out items with non-null archivedAt before returning active projects.

Useful? React with 👍 / 👎.

preferredModel,
workingDirs,
useWorktrees,
order: 0,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve existing project order during update

updateProject() rewrites project properties with order: 0 on every edit, which overwrites any order set by reorderProjects() and causes edited projects to jump in views that sort by order. The update path should carry forward the current order (or leave it untouched) rather than hard-resetting it.

Useful? React with 👍 / 👎.

{filtered.map((skill) => (
<div
key={skill.name}
key={`${skill.global ? "g" : "p"}-${skill.name}`}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use unique keys for project-scoped skill rows

The row key only uses global + name, so two project-scoped skills from different projects with the same name produce duplicate React keys. This can cause row/menu state to be reused across items when includeProjectSources is enabled. Include a project discriminator (for example projectDir) in the key.

Useful? React with 👍 / 👎.

Comment on lines 840 to +844
"null"
],
"description": "Absolute path to the project root. Required when `global` is false."
},
"properties": {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add projectId to CreateSourceRequest schema

The ACP schema entry for CreateSourceRequest was updated for properties but still omits the new projectId request field introduced in Rust. That leaves generated SDK clients/types unable to send projectId, so typed consumers cannot use project-scoped source creation through the new convenience path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af6a5fc794

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

type: "project",
});
const sources = (raw.sources ?? []) as SourceEntry[];
return sources.map(toProjectInfo);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Sort projects by persisted order before returning

listProjects() now returns sources.map(toProjectInfo) directly, but _goose/sources/list is name-sorted and does not honor properties.order. This means every refetch (startup, refresh, after CRUD) can reshuffle the Projects view away from the user’s drag-and-drop order even though reorderProjects() persists order values. Sort by order (with a stable tiebreaker) before returning to preserve the saved project ordering.

Useful? React with 👍 / 👎.

Comment on lines +379 to +382
/// When true, also scan the working directories of all known projects for
/// project-scoped sources (e.g. skills stored under `{workingDir}/.agents/skills/`).
#[serde(default)]
pub include_project_sources: bool,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Regenerate ACP schema for includeProjectSources

ListSourcesRequest now defines include_project_sources in Rust, but the ACP schema/generated SDK contract still omits this field, so typed clients cannot request project-scoped source discovery without unsafe casts. This creates a contract drift between server behavior and published client types; update/regenerate schema artifacts to include includeProjectSources.

Useful? React with 👍 / 👎.

Move projects from Tauri IPC commands to the ACP sources system.

Backend:
- Add Project to SourceType, properties bag on SourceEntry
- Project storage in Paths::data_dir()/projects/
- System prompt injection in agent reply via load_project_instructions
- _goose/session/set_project ACP method
- includeProjectSources flag for listing skills from project working dirs
- projectId convenience on CreateSourceRequest for project-scoped skills

Frontend:
- Rewrite projects API from Tauri invoke to ACP ext methods
- Remove buildProjectSystemPrompt (backend handles it now)
- Filter archived projects, sort by persisted order
- updateProject preserves existing order
- Add project picker to CreateSkillDialog
- Show project badges on non-global skills
- Fix SDK listSessions rename, TS strict errors

Delete ui/goose2/src-tauri/src/commands/projects.rs (508 lines).

Signed-off-by: Douwe Osinga <douwe@squareup.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dccd332f90

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +235 to +236
const all = await listProjects();
return all.filter((p) => p.archivedAt !== null);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Read archived projects from an unfiltered source list

listArchivedProjects() now calls listProjects(), but listProjects() already drops every project with archivedAt !== null, so this function can only ever return an empty array. In ui/goose2/src/features/settings/ui/SettingsModal.tsx, archived project management is populated from listArchivedProjects(), so users cannot see or restore archived projects after archiving them.

Useful? React with 👍 / 👎.

Comment on lines +131 to +132
global: !options?.projectId,
...(options?.projectId ? { projectId: options.projectId } : {}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve projectId before calling _goose/sources/import

When importSkills() is called with options.projectId, the request sends global: false plus projectId, but the backend import contract only accepts projectDir for scoped imports (ImportSourcesRequest), so projectId is ignored and the server receives a project-scoped import with no projectDir. That path fails validation (projectDir is required when global is false), so project-scoped skill imports are broken for any caller that uses this new option.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4ef532a9c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +122 to +124
const raw = await client.extMethod("_goose/sources/update", {
type: "project",
name: existing.id,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use supported update contract for project mutations

updateProject() is now wired to _goose/sources/update but sends { type, name, ... } without the required path, and the same payload shape is reused by archiveProject, restoreProject, and reorderProjects. The ACP UpdateSourceRequest requires path, and the backend update path still enforces skill-only updates, so these project edit/archive/reorder flows fail instead of persisting changes.

Useful? React with 👍 / 👎.

Comment on lines +145 to +147
await client.extMethod("_goose/sources/delete", {
type: "project",
name: id,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Call project delete with a valid backend identifier

deleteProject() calls _goose/sources/delete with { type, name, global }, but the delete contract requires a path field and the backend delete implementation is skill-path based. In practice this means project deletion requests fail at runtime, so users cannot remove projects from the UI.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 311fd33b3e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

fn projects_dir() -> PathBuf {
Paths::data_dir().join("projects")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add legacy project migration/fallback before switching storage

This hard-switches project storage to Paths::data_dir()/projects and only reads .md sources from that directory, but the deleted desktop path stored existing user projects under ~/.goose/projects/*/project.json. Without a migration or fallback read path, upgraded users will see their existing projects disappear (and thread project_id values pointing to old project IDs will no longer resolve instructions), which is a data-visibility regression for all existing installs.

Useful? React with 👍 / 👎.


useChatSessionStore.getState().updateSession(sessionId, { projectId });

void setSessionProject(sessionId, projectId).catch(console.error);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Await project metadata update before relying on backend prompt

After removing frontend project prompt injection, backend thread metadata is now authoritative for project instructions, but project changes call setSessionProject in fire-and-forget mode (void ...catch(...)). If the user sends a message immediately (or the request is delayed/fails), the UI state shows the new project while the backend still uses the old project for prompt construction, so at least one reply can be generated with the wrong project context.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6d6cd3b98b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +141 to +143
await createSkill(name, description.trim(), instructions,
selectedProjectId !== "global" ? { projectId: selectedProjectId } : undefined,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wire project-scoped skill selection into createSkill

The new "Save to" control passes a 4th argument to createSkill, but the helper in features/skills/api/skills.ts still only accepts (name, description, instructions) and always sends global: true. That means this new argument is ignored at runtime, so choosing a project still creates a global skill and project-scoped creation from this dialog does not work.

Useful? React with 👍 / 👎.

Comment on lines +18 to +21
"saveLocation": "Save to",
"saving": "Saving...",
"scopeGlobal": "Global",
"saveLocationHint": "Global skills are available in all sessions. Project skills are stored in the project directory."
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add locale entries for new skill scope labels

This commit introduces new dialog keys for skill save scope, but only the English locale is updated. CreateSkillDialog now renders these keys, so non-English locales (for example locales/es/skills.json) will show fallback English/raw-key text for this new UI. Please add matching keys across supported locales when adding new i18n strings.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 690f564851

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/goose/src/sources.rs Outdated
/// Read a single project source by name.
pub fn read_project(name: &str) -> Result<SourceEntry, Error> {
let dir = projects_dir();
let file = dir.join(format!("{name}.md"));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate project IDs before resolving project file paths

read_project() directly interpolates name into a filesystem path ({name}.md) without validating or normalizing it, and project_id is accepted from session metadata/update methods as arbitrary user input. A value like ../../tmp/evil can escape the projects directory and cause unrelated markdown files to be read as project instructions (or working-dir metadata), so project IDs should be validated (e.g., same slug rules as project creation) before path construction.

Useful? React with 👍 / 👎.

@@ -254,6 +254,7 @@ describe("CreateSkillDialog", () => {
"my-skill",
"A description",
"Some instructions",
{ projectId: undefined },
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fix dialog API call assertions to match actual arguments

This test now expects createSkill to receive { projectId: undefined }, but the dialog passes undefined for the 4th arg in the global-scope path; similarly, another updated assertion expects an extra 5th arg for updateSkill that is never sent. These mismatched expectations make the tests fail even when behavior is correct, creating a false-negative CI blocker.

Useful? React with 👍 / 👎.

@@ -0,0 +1,3 @@
{
"binaryPath": "/Users/douwe/proj/wt-goose-4/ui/goose-binary/goose-binary-darwin-arm64/bin/goose"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Drop machine-specific binary path from tracked config

This new file hardcodes a developer-local absolute path (/Users/douwe/...) into the repository. On other machines that path is invalid, and any workflow that consults this file will fail to locate the binary; even if currently unused, this is a non-portable local artifact that should not be committed.

Useful? React with 👍 / 👎.

Douwe Osinga added 2 commits April 30, 2026 15:31
…esign

Merges main's path-based source addressing (#8731), redesigned skills library
(#8868), and 70+ other commits while preserving the projects-as-sources work.

Backend:
- SourceType gains Project alongside main's Skill/BuiltinSkill/Recipe/Subrecipe/Agent
- SourceEntry.directory renamed to .path so the field name matches its role
  (a stable path identifier passed back to update/delete/export, regardless
  of whether the underlying entity is a directory or file)
- SourceEntry.properties (free-form metadata bag) coexists with main's
  supporting_files
- ListSourcesRequest.include_project_sources scans every known project's
  workingDirs and tags resulting skills with projectName/projectDir
- CreateSourceRequest.project_id resolves to the project's first workingDir
- Projects stored as <dataDir>/projects/<slug>.md flat-file frontmatter
- read_project / project_working_dirs helpers used by agent.rs to inject
  project instructions into the system prompt
- Drops our SetSessionProjectRequest in favor of main's identical
  UpdateSessionProjectRequest (_goose/session/update_project)

Frontend:
- features/projects/api/projects.ts rewritten on the typed SDK; ProjectInfo
  loses createdAt/updatedAt/artifactsDir, gains path
- Skills API uses main's typed GooseSourcesCreate/List/Update/Delete/Export/
  Import calls. toSkillInfo prefers backend-provided properties.projectName/
  projectDir tags over path-derived guesses
- CreateSkillDialog keeps our save-location picker (Global vs each project)
  on top of main's redesigned UI; edits use path-based addressing
- SkillsView takes main's redesigned filter row + sectioned list rendering
- AppShell drops the old inline-type adapter
- Removes ui/goose2/src-tauri/src/commands/projects.rs (508 lines) and its
  9 Tauri command registrations; main's project_icons.rs (filesystem icon
  scanning, a real shell concern) is kept

Verification: cargo test -p goose passes (incl. 17 sources tests, 2 new
project ones); cargo clippy clean; pnpm tsc clean; pnpm test 538/538;
pnpm check clean.
Pulls in 7 new commits from main:
- #8932 break up acp/server.rs into submodules
- #8924 custom providers UI/ACP
- #8935 keep renamed skills open in detail view
- #8897 skill refactor (CreateSkillDialog -> SkillEditor)
- #8886 copy/content polish
- #8905 Gemini OAuth fix
- #8546 command injection patterns

Conflicts resolved:
- crates/goose/src/acp/server.rs: took main's split structure;
  added project-related plumbing (properties, project_id,
  include_project_sources) into crates/goose/src/acp/server/sources.rs.
- update_thread_metadata: pub(super) so server/sessions.rs (which
  hosts on_update_session_project) can call it.
- ui/goose2/src/features/skills/api/skills.ts: dropped
  directoryPath/editable (not on main's SkillInfo); kept our
  projectName/projectDir lookup from backend properties.
- ui/goose2/src/features/skills/ui/SkillEditor.tsx (renamed from
  CreateSkillDialog.tsx in #8897): kept main's structure; re-added
  our save-location picker (Global vs each project) and onSaved
  callback.
- ui/goose2/src/shared/i18n/locales/en/skills.json: took main's
  casing for editTitle/newTitle; kept our new keys (global,
  globalHint, projectHint, saveLocation).

Bumped useChatSessionController.ts file-size limit by 10 lines
(840 -> 850); justification updated to mention ACP project sync.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2115b76b1b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +75 to +76
path: source.path,
fileLocation: getSkillFileLocation(source.path),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Provide path fallback for source entries

This adapter now assumes every source has path, but existing ACP test fixtures still return directory (for example in ui/goose2/tests/e2e/fixtures/tauri-mock.ts), so source.path becomes undefined and downstream helpers like deriveProjectRoot() / getSkillFileLocation() crash on string operations. That makes mocked skills flows fail at runtime after this contract change; add a compatibility fallback when mapping (path or legacy directory) or update all fixtures in the same change.

Useful? React with 👍 / 👎.

Comment on lines +130 to +133
const id = slugify(name);
const raw = await client.extMethod("_goose/sources/create", {
type: "project",
name: id,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Disambiguate colliding project slugs on create

Project creation now uses slugify(name) directly as the persisted source ID, so different display names that normalize to the same slug (e.g. case/punctuation variants, or non-ASCII-only names collapsing to project) all send the same name to _goose/sources/create and the second create fails with "already exists." This is a user-facing regression in create flow; generate a unique slug (suffix strategy) before calling the backend.

Useful? React with 👍 / 👎.

Field renamed from 'directory' to 'path' in SourceEntry to reflect
its role as the stable on-disk identifier (works for both directory-
backed skills and flat-file projects). The e2e Tauri/ACP mock was
still emitting 'directory:' for skill source entries, so the
frontend's toSkillInfo() saw source.path === undefined and broke
in deriveProjectRoot() / getSkillFileLocation().

Also drops a stale doc comment on UpdateSourceRequest.properties
that described old replace-all semantics; keep the field
self-documenting via its name.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant